package cn.easyutil.util.javaUtil;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import cn.easyutil.util.javaUtil.bean.FileTypeEnum;

/**
 * 文件操作工具类
 * 
 * @author spc
 *
 */
public class FileUtil {

	/**
	 * 判断是否图片类型
	 * 
	 * @param input
	 * @return
	 */
	public static boolean isPicture(InputStream input) {
		byte[] but = IOUtil.inputToByte(input);
		return isPicture(but);
	}

	/**
	 * 判断是否视频类型
	 * 
	 * @param input
	 * @return
	 */
	public static boolean isVideo(InputStream input) {
		byte[] but = IOUtil.inputToByte(input);
		return isVideo(but);
	}

	/**
	 * 判断是否文本类型
	 * 
	 * @param input
	 * @return
	 */
	public static boolean isDoc(InputStream input) {
		byte[] but = IOUtil.inputToByte(input);
		return isDoc(but);
	}

	/**
	 * 判断是否音频类型
	 * 
	 * @param input
	 * @return
	 */
	public static boolean isAudio(InputStream input) {
		byte[] but = IOUtil.inputToByte(input);
		return isAudio(but);
	}

	/**
	 * 判断文件是否音频类型
	 * 
	 * @param but
	 *            文件byte
	 * @return
	 */
	public static boolean isAudio(byte[] but) {
		return FileTypeEnum.audiosTypes().contains(getType(but));
	}

	/**
	 * 判断文件是否文本类型
	 * 
	 * @param but
	 *            文件byte
	 * @return
	 */
	public static boolean isDoc(byte[] but) {
		return FileTypeEnum.docTypes().contains(getType(but));
	}

	/**
	 * 判断文件是否视频类型
	 * 
	 * @param but
	 *            文件byte
	 * @return
	 */
	public static boolean isVideo(byte[] but) {
		return FileTypeEnum.videoTypes().contains(getType(but));
	}

	/**
	 * 判断文件是否图片类型
	 * 
	 * @param but
	 *            文件byte
	 * @return
	 */
	public static boolean isPicture(byte[] but) {
		return FileTypeEnum.pictureTypes().contains(getType(but));
	}

	/**
	 * 获取文件类型
	 * 
	 * @param input
	 *            文件的输入流
	 * @return
	 */
	public static FileTypeEnum getType(InputStream input) {
		byte[] but = IOUtil.inputToByte(input);
		return getType(but);
	}

	/**
	 * 获取文件类型
	 * 
	 * @param but
	 *            文件byte
	 * @return
	 */
	public static FileTypeEnum getType(byte[] but) {
		if (but == null || but.length < 28) {
			throw new RuntimeException("文件解析错误");
		}
		byte[] data = new byte[28];
		for (int i = 0; i < data.length; i++) {
			data[i] = but[i];
		}
		String header = bytesToHexString(data); // 00000020667479706d70
		return FileTypeEnum.getFileTypeByHeader(header);// 000000186674797069736f6d0000000169736f6d61766331000023a1

	}

	/**
	 * 文件转byte
	 * 
	 * @param filePath
	 * @return
	 */
	public static byte[] toByteArray(String filePath) {
		File file = new File(filePath);
		return toByteArray(file);
	}

	/**
	 * 文件读取流
	 * 
	 * @param filePath
	 * @return
	 */
	public static InputStream toInput(String filePath) {
		try {
			return new FileInputStream(new File(filePath));
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	
	/** 获取文件名 */
	public static String getFileName(String url) {
		url = url.replace("/", File.separator).replace("\\\\", File.separator);
		return url.substring(url.lastIndexOf(File.separator)+1);
	}
	
	/** 获取扩展名 */
	public static String getSuffix(String url) {
		if(!url.contains(".")) {
			return null;
		}
		return url.substring(url.lastIndexOf(".")+1);
	}
	
	
	/**
	 * 拷贝文件(支持文件夹全拷贝)(流读写)
	 * @param from	从哪里拷贝
	 * @param to	粘贴到哪里
	 * @param executors	文件过滤器
	 */
	@SafeVarargs
	public static void copy(String from, String to, FileExecutor<?>... executors) {
		from = from.replace("/", File.separator).replace("\\\\", File.separator);
		to = to.replace("/", File.separator).replace("\\\\", File.separator);
		File fromFile = new File(from);
		if(!fromFile.exists()) {
			LoggerUtil.error(FileUtil.class, "拷贝的源文件不存在 [ "+from+" ]");
			return;
		}
		if(fromFile.isDirectory()) {
			File dirFile = new File(to);
			if(!dirFile.exists()) {
				dirFile.mkdirs();
			}
			for(File sonFile : fromFile.listFiles()) {
				// 处理过滤
				boolean filterRresult = true;
				if(executors != null) {
					for(FileExecutor<?> filter : executors) {
						filterRresult &= filter.doExec(sonFile);
					}
				}
				if(filterRresult) {
					copy(from+File.separator+sonFile.getName(), to+File.separator+sonFile.getName(), executors);
				}
			}
		} else {
			if(to.lastIndexOf(File.separator) == to.length()-1) {
				new File(to).mkdirs();
				to += fromFile.getName();
			}
			String parentDir = to.substring(0, to.lastIndexOf(File.separator));
			File dirFile = new File(parentDir);
			if(!dirFile.exists()) {
				dirFile.mkdirs();
			}
			try {
				FileInputStream in = new FileInputStream(fromFile);
				save(in, new File(to));
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}
	
	/**
	 * 拷贝文件(支持文件夹全拷贝)(字符串读写)
	 * @param from	从哪里拷贝
	 * @param to	粘贴到哪里
	 * @param executors	文件过滤器
	 */
	public static void copyWithString(String from, String to, FileExecutor<?>... executors) {
		from = from.replace("/", File.separator).replace("\\\\", File.separator);
		to = to.replace("/", File.separator).replace("\\\\", File.separator);
		File fromFile = new File(from);
		if(!fromFile.exists()) {
			throw new RuntimeException("拷贝的源文件不存在 [ "+from+" ]");
		}
		if(fromFile.isDirectory()) {
			File dirFile = new File(to);
			if(!dirFile.exists()) {
				dirFile.mkdirs();
			}
			for(File sonFile : fromFile.listFiles()) {
				// 处理过滤
				boolean filterRresult = true;
				if(executors != null) {
					for(FileExecutor<?> filter : executors) {
						filterRresult &= filter.doExec(sonFile);
					}
				}
				if(filterRresult) {
					to = to.lastIndexOf(File.separator)==to.length()-1?to:(to+File.separator);
 					copyWithString(from+File.separator+sonFile.getName(), to+sonFile.getName(), executors);
				}
			}
		} else {
			if(to.lastIndexOf(File.separator) == to.length()-1) {
				new File(to).mkdirs();
				to += fromFile.getName();
			}
			String parentDir = to.substring(0, to.lastIndexOf(File.separator));
			File dirFile = new File(parentDir);
			if(!dirFile.exists()) {
				dirFile.mkdirs();
			}
			saveWithString(fromFile, new File(to), executors);
		}
	}
	
	/**
	 * 保存文件到本地(字符串读写)
	 * @param in	输入流
	 * @param path	保存到
	 */
	public static void saveWithString(File from, File to, FileExecutor<?>... executors) {
		String suffix = getSuffix(from.getName());
		suffix = suffix==null?null:suffix.toLowerCase();
		switch (suffix) {
			case "zip": case "rar": case "jar": case "war": case "jpg": case "png": case "gif": {
				copy(from.getAbsolutePath(), to.getAbsolutePath(), executors);
				return;
			}
		}
		BufferedReader in = null;
		BufferedWriter out = null;
		try {
			in = new BufferedReader(new FileReader(from));
			String line = in.readLine();
			out = new BufferedWriter(new FileWriter(to));
			while(line != null) {
				if(executors != null) {
					for(FileExecutor<?> executor : executors) {
						line = executor.replace(line, from, to);
					}
				}
				if(line != null) {
					out.append(line);
					out.newLine();
				}
				line = in.readLine();
			}
			out.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (out != null) {
				try {
					out.close();
					in.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	/**
	 * 保存文件到本地
	 * 
	 * @param in
	 * @param path
	 */
	public static void save(InputStream in, File file) {
		FileOutputStream out = null;
		try {
			out = new FileOutputStream(file);
			out.write(IOUtil.inputToByte(in));
			out.flush();
		} catch (IOException e) {
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	/**
	 * 文件转byte
	 * 
	 * @param file
	 * @return
	 */
	public static byte[] toByteArray(File file) {
		FileInputStream fin = null;
		ByteArrayOutputStream out = null;
		try {
			out = new ByteArrayOutputStream();
			fin = new FileInputStream(file);
			byte[] but = new byte[1024 * 1024];
			int length = 0;
			while ((length = fin.read(but)) != -1) {
				out.write(but, 0, length);
			}
			return out.toByteArray();
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				fin.close();
				out.close();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 将文件头转换成16进制字符串
	 * 
	 * @return 16进制字符串
	 */
	private static String bytesToHexString(byte[] src) {

		StringBuilder stringBuilder = new StringBuilder();
		if (src == null || src.length <= 0) {
			return null;
		}
		for (int i = 0; i < src.length; i++) {
			int v = src[i] & 0xFF;
			String hv = Integer.toHexString(v);
			if (hv.length() < 2) {
				stringBuilder.append(0);
			}
			stringBuilder.append(hv);
		}
		return stringBuilder.toString();
	}

	public static String read(File file, Charset charset){
		byte[] bytes = toByteArray(file);
		return new String(bytes,charset);
	}
	
	/** 删除文件（支持有文件的文件夹） */
	public static void del(String url, FileExecutor<?>... fce) {
		File f = new File(url);
		if(!f.exists()) {
			return;
		}
		if(fce!=null && fce.length>0) {
			for(FileExecutor<?> ce : fce) {
				if(!ce.doExec(f)) {
					return;
				}
			}
		}
		if(f.isDirectory()) {
			for(File son : f.listFiles()) {
				del(son.getAbsolutePath(), fce);
			}
		} 
		f.delete();
	}
	
	/***
	 * 修改文件内容
	 * @param fileUrl	文件地址
	 * @param re	修改器
	 */
	public static void change(String fileUrl, FileExecutor<?> re) {
		File param = new File(fileUrl);
		if(!param.exists()) {
			throw new RuntimeException("修改的源文件不存在 [ "+fileUrl+" ]");
		}
		if(param.isDirectory()) {
			for(File sonFile : param.listFiles()) {
				if(re.doExec(sonFile)) {
					change(sonFile.getAbsolutePath(), re);
				}
			}
		} else {
			List<String> contends = new ArrayList<>();
			BufferedWriter out = null;
			try {
				BufferedReader in = new BufferedReader(new FileReader(fileUrl));
				String line = in.readLine();
				while(line != null) {
					line = re.replace(line, new File(fileUrl), new File(fileUrl));
					contends.add(line);
					line = in.readLine();
				}
				in.close();
				
				out = new BufferedWriter(new FileWriter(fileUrl));
				for(String newStr : contends) {
					out.append(newStr);
					out.newLine();
				}
				out.flush();
			} catch (IOException e) {
				throw new RuntimeException(e);
			} finally {
				if (out != null) {
					try {
						out.close();
					} catch (IOException e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
	}
	
	/** 读取文本文件 */
	public static String readFile(String fileUrl) {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		FileInputStream fin = null;
		String str = null;
		try {
			fin = new FileInputStream(fileUrl);
			copy(fin, bout);
			str = new String(bout.toByteArray(), "UTF-8");
		} catch (FileNotFoundException e) {
			LoggerUtil.error(FileUtil.class, "要读取的源文件不存在[ "+fileUrl+" ]");
			return null;
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				bout.close();
				if(fin != null) {
					fin.close();
				}
			} catch (Exception e2) {
				LoggerUtil.error(FileUtil.class, "文件读取流关闭失败[ "+fileUrl+" ]", e2);
			}
		}
		return str;
	}
	
	/** 写出 */
	public static void copy(InputStream in, OutputStream out) {
		try {
			byte[] bts = new byte[1024];
			int length = 0;
			while((length=in.read(bts)) > 0) {
				out.write(bts, 0, length);
			}
			out.flush();
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				in.close();
				out.close();
			} catch (Exception e2) {
				throw new RuntimeException(e2);
			}
		}
	}

	/** 重命名(移动文件)(先拷贝，再删除) */
	public static void rename(String from, String to, FileExecutor<?>... res) {
		if(res!=null && res.length>0) {
			copyWithString(from, to, res);
		} else {
			copy(from, to, res);
		}
		del(from);
	}
	
	/** 文件拷贝替换器(多个替换器会按顺序执行) */
	public static abstract class FileExecutor<T> {
		T trunk = null; 
		public FileExecutor(T trunk) {
			this.trunk = trunk;
		}
		
		public FileExecutor(){}
		
		/** (子类重写方法)将原数据替换为新数据(copy = from, to) */
		public String replace(String source, File from, File to) {
			return source;
		}
		
		/** (子类重写方法)过滤方法（false：不进行拷贝） */
		public boolean doExec(File f) {
			return true;
		}

		public T getTrunk() {
			return (T) trunk;
		}

		public void setTrunk(T trunk) {
			this.trunk = trunk;
		}
	}
}
